/*
 * $RCSfile: ConnectionManager.java,v $
 * $Revision: 1.1 $
 * $Date: 2013-12-15 $
 *
 * Copyright (C) 2008 Skin, Inc. All rights reserved.
 *
 * This software is the proprietary information of Skin, Inc.
 * Use is subject to license terms.
 */
package com.skin.webcat.datasource;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.skin.webcat.config.Config;
import com.skin.webcat.database.mysql.MySql;
import com.skin.webcat.util.ClassUtil;
import com.skin.webcat.util.Jdbc;

/**
 * <p>Title: ConnectionManager</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2006</p>
 * @author xuesong.net
 * @version 1.0
 */
public class ConnectionManager {
    private static final Map<String, ConnectionConfig> map = load();
    private static Logger logger = LoggerFactory.getLogger(ConnectionManager.class);

    /**
     * @param name
     * @return Connection
     * @throws SQLException
     */
    public static Connection getConnection(String name) throws SQLException {
        ConnectionConfig connectionConfig = getConfig(name);
        return connect(connectionConfig, null);
    }

    /**
     * @param name
     * @param database
     * @return Connection
     * @throws SQLException
     */
    public static Connection getConnection(String name, String database) throws SQLException {
        ConnectionConfig connectionConfig = getConfig(name);
        return connect(connectionConfig, database);
    }

    /**
     * @param connectionConfig
     * @param database
     * @return Connection
     * @throws SQLException
     */
    public static Connection connect(ConnectionConfig connectionConfig, String database) throws SQLException {
        String url = getUrl(connectionConfig, database);
        String driverClass = connectionConfig.getDriverClass();
        String userName = connectionConfig.getUserName();
        String password = connectionConfig.getPassword();
        String properties = connectionConfig.getProperties();

        if(properties != null) {
            return Jdbc.connect(url, driverClass, properties);
        }
        else {
            return Jdbc.connect(url, driverClass, userName, password);
        }
    }

    /**
     * @param connectionConfig
     * @param database
     * @return String
     */
    public static String getUrl(ConnectionConfig connectionConfig, String database) {
        String url = connectionConfig.getUrl();

        /**
         * 未来可根据不同的情况返回不同的数据库连接
         */
        if(url.indexOf("jdbc:mysql://") > -1) {
            return getMysqlUrl(connectionConfig, database);
        }
        else {
            return url;
        }
    }

    /**
     * @param connectionConfig
     * @param database
     * @return String
     */
    public static String getMysqlUrl(ConnectionConfig connectionConfig, String database) {
        if(database == null) {
            return connectionConfig.getUrl();
        }

        String url = connectionConfig.getUrl();
        String host = MySql.getHost(url, true);
        StringBuilder buffer = new StringBuilder();
        buffer.append("jdbc:mysql://");
        buffer.append(host);
        buffer.append("/");
        buffer.append(database);
        buffer.append("?autoReconnect=true");
        buffer.append("&zeroDateTimeBehavior=convertToNull");
        buffer.append("&useUnicode=true");
        buffer.append("&characterEncoding=utf-8");
        buffer.append("&useNewIO=true");
        buffer.append("&rewriteBatchedStatements=true");
        buffer.append("&CharSet=utf8mb4");
        buffer.append("&remarks=true");
        buffer.append("&useInformationSchema=true");
        return buffer.toString();
    }

    /**
     * @param name
     * @return ConnectionConfig
     */
    public static ConnectionConfig getConfig(String name) {
        return map.get(name);
    }

    /**
     * @return Map<String, ConnectionConfig>
     */
    public static Map<String, ConnectionConfig> load() {
        Map<String, ConnectionConfig> map = new LinkedHashMap<String, ConnectionConfig>();
        List<ConnectionConfig> configList = getConnectionConfigs();

        if(configList != null) {
            for(ConnectionConfig connectionConfig : configList) {
                map.put(connectionConfig.getName(), connectionConfig);
            }
        }
        return map;
    }

    /**
     * @return List<String>
     */
    public static List<String> getConfigNameList() {
        List<String> list = new ArrayList<String>();
        
        for(ConnectionConfig connectionConfig : map.values()) {
            list.add(connectionConfig.getName());
        }
        return list;
    }

    /**
     * @return List<ConnectionConfig>
     */
    public static Collection<ConnectionConfig> getConnectionList() {
        return map.values();
    }

    /**
     * @return List<ConnectionConfig>
     */
    private static List<ConnectionConfig> getConnectionConfigs() {
        InputStream inputStream = Config.getInputStream("META-INF/conf/database.xml");

        if(inputStream != null) {
            try {
                return ConnectionManager.getConnectionConfigs(new InputStreamReader(inputStream, "utf-8"));
            }
            catch(Exception e) {
                logger.error(e.getMessage(), e);
            }
            finally {
                try {
                    inputStream.close();
                }
                catch(IOException e) {
                }
            }
        }
        return null;
    }

    /**
     * @param reader
     * @return List<ConnectionConfig>
     * @throws Exception
     */
    private static List<ConnectionConfig> getConnectionConfigs(Reader reader) throws Exception {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.parse(new InputSource(reader));
        Element element = document.getDocumentElement();
        NodeList childNodes = element.getChildNodes();
        List<ConnectionConfig> list = new ArrayList<ConnectionConfig>();

        for(int i = 0, length = childNodes.getLength(); i < length; i++) {
            Node node = childNodes.item(i);
            int nodeType = node.getNodeType();

            if(nodeType == Node.ELEMENT_NODE) {
                String nodeName = node.getNodeName();

                if(nodeName.equals("connection")) {
                    ConnectionConfig connectionConfig = new ConnectionConfig();
                    connectionConfig.setType(getAttribute(node, "type", "jdbc"));
                    mapping(node, connectionConfig, false);

                    if(connectionConfig.getName() != null && connectionConfig.getName().trim().length() > 0) {
                        list.add(connectionConfig);
                    }
                }
            }
        }
        return list;
    }
    
    /**
     * @param node
     * @param object
     * @param capitalization
     */
    private static void mapping(Node node, Object object, boolean capitalization) {
        String name = null;
        String methodName = null;
        Class<?> type = object.getClass();
        Method[] methods = type.getMethods();
        NodeList childNodes = node.getChildNodes();

        for(int i = 0; i < methods.length; i++) {
            methodName = methods[i].getName();
            Class<?>[] parameterTypes = methods[i].getParameterTypes();

            if(methodName.startsWith("set") && parameterTypes.length == 1) {
                name = methodName.substring(3);
                name = (capitalization ? name : java.beans.Introspector.decapitalize(name));
                Object value = ClassUtil.cast(getNodeContent(childNodes, name), parameterTypes[0]);

                if(value != null) {
                    try {
                        methods[i].invoke(object, new Object[]{value});
                    }
                    catch(IllegalArgumentException exception) {
                    }
                    catch(IllegalAccessException exception) {
                    }
                    catch(InvocationTargetException exception) {
                    }
                }
            }
        }
    }

    /**
     * @param node
     * @param name
     * @param defaultValue
     * @return String
     */
    private static String getAttribute(Node node, String name, String defaultValue) {
        NamedNodeMap attributes = node.getAttributes();

        if(attributes != null) {
            Node item = attributes.getNamedItem(name);
    
            if(item != null) {
                return item.getTextContent();
            }
        }
        return defaultValue;
    }

    /**
     * @param childNodes
     * @param name
     * @return String
     */
    private static String getNodeContent(NodeList childNodes, String name) {
        for(int i = 0, length = childNodes.getLength(); i < length; i++) {
            Node node = childNodes.item(i);
            int nodeType = node.getNodeType();

            if(nodeType == Node.ELEMENT_NODE) {
                String nodeName = node.getNodeName();
                
                if(nodeName.equals(name)) {
                    return node.getTextContent();
                }
            }
        }
        return null;
    }
}
